docs(spec): publish the solution-blueprint module header instead of its export list - #16992
Merged
zhuangjianguo merged 4 commits intoSep 9, 2026
Merged
Conversation
`findModuleDocBlock()` rejects a header-zone doc block that is immediately followed by a declaration — TSDoc's own attachment rule, read back: such a block documents that symbol. In `ai/solution-blueprint.zod.ts` a single blank line separated the module header from `const SNAKE_CASE`, so the header was read as that regex constant's documentation and disqualified. The generators then fell through to the export-list fallback, and the published skill-index row stated a true fact about the file while saying nothing about its subject: - `.../ai/solution-blueprint.zod.ts` — Exports: BlueprintConditionSchema, ... Give `SNAKE_CASE` the one-line doc it always deserved. That is a comment, not a declaration, so the preamble now ends there and the header — plan-first authoring, the `apply_blueprint` expansion — becomes the module's own block. The selector is untouched: it was deciding correctly under its own rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
The published row and the reference page both regain the file's own sentence: - `.../ai/solution-blueprint.zod.ts` — Solution Blueprint Schema (ADR-0033 §4 — plan-first authoring) Neither generator can see this class on its own — `check:skill-refs` and `check:docs` compare the artifact against the generator, and the generator reproduces the selector faithfully, so a generator-only check passes on the defect. The pin asserts the fact the artifact must state instead. Two legs that fail differently: the SOURCE leg reds the moment the separator between the header and `SNAKE_CASE` goes away; the CORPUS leg stays green through that and reds once an index is regenerated from a file whose header no longer qualifies. Both directions measured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
The pin reads `skills/*/references/_index.md` — outside its own package — so it belongs in `vitest.repo-tests.json` and runs under the `repo` project, whose hash moves with what it actually reads. Left undeclared it would run under `@objectstack/spec#test`, invisible to both the affected-subset filter and the turbo cache: green on every PR while red on main, which is the one failure this pin exists to refuse. `packages/spec` ships `src/**/*.zod.ts`, and `npm pack --dry-run` confirms the edited source is in the tarball (206 such entries ship; scripts/, skills/ and content/ ship none), so the change publishes and takes a patch changeset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
The pin and its `vitest.repo-tests.json` declaration are a change to `packages/spec/scripts/`, i.e. to the generator side of this repo. Carried here they defeat the generated-artifact exception that would otherwise lift the `skills/**` path on this PR: the recompute that certifies the regenerated index would be running a tree that also modifies the generator it is trusting, so it cannot certify itself. What is left here is a pure `gen:skill-refs` / `gen:docs` regeneration beside the one-line source fix. The pin lands separately and is not weakened. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
This was referenced Sep 9, 2026
zhuangjianguo
marked this pull request as ready for review
September 9, 2026 00:51
zhuangjianguo
enabled auto-merge
September 9, 2026 00:52
zhuangjianguo
deleted the
claude/issue-15263-solution-blueprint-module-header
branch
September 9, 2026 01:17
This was referenced Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #15263
Clause-②: no
The change makes an already-written module header visible to the header selector.
No accept set moves, no export is added, removed or renamed, no schema arm, bound or
behaviour changes. The published skill-index row gains the sentence the file already
contained. 拉回已声明契约 ⇒ 常规档.
The defect, in the content of the published line
Before — a true statement about the file that says nothing about its subject, on the one
row whose job is to send an agent to this source for exact field shapes:
After:
The public reference page
content/docs/references/ai/solution-blueprint.mdxgained the sameheader as its opening paragraph — it had been printing none at all.
The mechanism, and why the fix is in the source
findModuleDocBlock()requires as its third condition that a header-zone block documentnothing, and "immediately followed by a declaration" tolerates only blank lines between. One
blank line separated this file's module header from the snake_case regex constant, so TSDoc's
own attachment rule made the header that constant's documentation and the selector disqualified
it. Both generators then fell through to their export-list fallback.
The one-line doc that constant always deserved is a comment, not a declaration, so the preamble
now ends there and the header becomes the module's own block.
The selector is untouched. Under its own rule it was deciding correctly — this is the same
root cause as #14441 pointing the other way. There the wrong block was published; here the right
block was suppressed and a machine-generated list took its place. That fix swapped a block; this
one gives an existing block the separator it needed.
Census — one file, not a family
The card made the re-grade conditional on how many files lose a header the same way, so the
count was measured first, by driving the real selector rather than restating its rule.
.tssources underpackages/spec/srcscanned*.zod.ts— the only set either generator readsThe 89 is the mechanical shape; it is not 89 defects. In 18 of the 19 shipped files the block
plainly documents the symbol it sits against —
Transport Protocol Enumagainstexport const TransportProtocol,Shared history for this file (#4001).againstAGENT_HISTORY,Field Widget Props SchemaagainstFieldWidgetPropsSchema. That is the selector doing exactlythe job it was built for. Only here does a module header sit against a constant it says nothing
about. 1 is below the "more than 3" re-grade trigger, so this stays one file at p3.
Controls on the census:
ai/solution-blueprint.zod.tsis PRESENT in the hit set (it fires), andapi/analytics.zod.ts— whose header survives today via the banner clause — is correctly absent.A first pass conflated two suppression mechanisms and counted 53 shipped files; the corrected
oracle asks the selector on a synthetic source where the only branch that can return null is the
declaration check, which separates this card's mechanism from the #13263/#13334 import clause
(34 files sit in that second group and are out of scope here).
Why a generator-only check could not stand in for a real acceptance test
Triage's own words, kept because they are the acceptance bar:
check:skill-refsandcheck:docseach compare an artifact against the generator that wrote it,and the generator reproduced the selector faithfully — so both were green throughout the defect.
That is why the acceptance evidence above is the content of the published row, and why the
pin in #16994 asserts that content directly rather than asserting that the generators ran.
Governed-surface verdict
gen:skill-refswrites intoskills/**, which is on the Prime Directive #14 register. Measuredon this PR's exact final file list:
exit 0. The lift depends on this PR containing no generator edit, which is exactly why the pin
was split out.
Verification
pnpm --filter @objectstack/spec check:generated— ✓ all 15 generated artifacts up to date, aftergen:skill-refsandgen:docs.git statusclean. (A first run reportedapi-surface/stale; that was a staledistin a freshly recreated worktree — it reads the built.d.ts, notsrc/. Green after a rebuild, with nothing regenerated.)pnpm --filter @objectstack/spec test— 467 files / 13100 tests passed;typecheck✓ including the test layer.pnpm lint— ✓ over the whole repository, so no narrowing is claimed.check:cross-package-test-inputs,check:test-source-alias,check:nul-bytes,check:published-files,check:skill-examples,check:skill-frame-sync,check:skill-compatibility,check:skill-identifier-liveness,check:doc-anchors,check:docs-single-h1,check:type-check-coverage,check:type-check-debt,check-empty-changeset,check-changeset-no-major,check-adr-0087-registration.scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, not from a hand-written list.Changeset — both halves
Half 1:
packages/spec'sfiles[]includessrc/**/*.zod.ts, and the edited file is one.Half 2, measured rather than assumed —
npm pack --dry-run --jsonon the deciding tree, the@objectstack/spectarball:src/ai/solution-blueprint.zod.tsis shipped, with 206src/**/*.zod.tsentries as the positive control, andscripts/,skills/andcontent/ship0 entries each as discriminating negative controls. So the skill index and the reference page
publish nothing, while the edited source does. ⇒ a
patchchangeset, notskip-changeset.Nothing under
prompts/orllms.txtmoved, and neither carries a per-file index row for thissource.
skills/** readings
skills/objectstack-ai/references/_index.mdSKILL.md)The row was replaced one-for-one and no
SKILL.mdmoved at all, so no budget is consumed.维护者速读(草稿)
改了什么
给
packages/spec/src/ai/solution-blueprint.zod.ts里的 snake_case 正则常量补了一行文档注释。连带重新生成了两个产物:
objectstack-ai技能索引里的那一行,和该文件的公开参考页。加一条 changeset。源码实际改动就是 1 行。配套的 pin 测试拆到 #16994。
为什么改
这个文件本来就写了模块头(ADR-0033 §4,plan-first 授权、
apply_blueprint展开),但它和后面的常量之间只隔一个空行,按 TSDoc 的归属规则就成了那个常量的文档,于是被选择器判掉。发布出去的那一行退化成
机器生成的导出清单 —— 陈述的是真事,却对这个文件是干什么的只字未提,而这行恰恰是引导 AI agent 去读
源码取字段形状的那一行。信息量的损失,不是错误指引。
风险与代价(含回滚)
风险很低:改的是注释,运行期零影响,没有任何导出、schema 或契约移动。发布面上动的只有 npm 包里那份
源码的字节。回滚就是还原这一行注释再跑一次两个生成器。本 PR 触到
skills/**,但 #11705 生成物例外已实测解除(见上方判定,exit 0)—— 前提是本 PR 不含生成器改动,这正是把 pin 拆走的原因。
席位意见
(留空,待席位填写)
你要做的
确认两点:① 发布出去的那一行现在说的是这个文件的主题,而不是导出清单;② 普查结论 —— 同机制在已发布的
19 个
*.zod.ts里只有这一处是误判,其余 18 处选择器判得对,所以本卡维持 p3、维持"修这个文件"而不是"修选择器"。若你认为 18 处里有哪一处其实也是误判,那就是重判信号,请指出来。
Generated by Claude Code